Adding Site Content Automation#71
Merged
Merged
Conversation
shoverbj
commented
Jun 11, 2026
Contributor
- Automatic blog post creation from Issue form
- Adding new blog authors to the author list
# Conflicts: # blog/authors.yml
Contributor
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
scouting331site | 908ad4d | Commit Preview URL Branch Preview URL |
Jun 19 2026, 06:48 PM |
| """ | ||
|
|
||
| import json | ||
| import yaml |
Comment on lines
+22
to
+46
| runs-on: ubuntu-latest # Provisions a fresh virtual machine Linux runner workspace environment | ||
| outputs: # Maps global outputs from local tracking steps to make them accessible across jobs | ||
| has_new_post: ${{ steps.blog_data.outputs.has_new_post }} | ||
| title: ${{ steps.blog_data.outputs.title }} | ||
| slug: ${{ steps.blog_data.outputs.slug }} | ||
| targets: ${{ steps.blog_data.outputs.targets }} # Contains the stringified JSON target array e.g., ["TROOP_303", "PACK_303"] | ||
| steps: | ||
| - name: Checkout repository # Step 1: Fetches repository source documentation onto the runner platform | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 2 # Forces checking history depths of 2 commits so git diff can track HEAD~1 shifts | ||
|
|
||
| - name: Set up Python # Step 2: provisions Python interpreter infrastructure binaries | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.x' # Always downloads the latest functional patch build iteration of Python 3 | ||
|
|
||
| - name: Parse Metadata via Python # Step 3: Run router script to identify targeting parameters | ||
| id: blog_data # Step identifier token used by outputs map fields to find variables | ||
| run: python .github/scripts/fb_router.py | ||
|
|
||
| # ---------------------------------------------------------------------------- | ||
| # JOB 2: Run dynamic multi-process loops to dispatch entries onto Facebook Pages | ||
| # ---------------------------------------------------------------------------- | ||
| facebook-routing: |
Comment on lines
+47
to
+92
| needs: parse-blog # Inter-job dependency lock: halts execution until data collection completes | ||
| # Execution gate: only runs if a post was successfully identified and targets list array contains entries | ||
| if: needs.parse-blog.outputs.has_new_post == 'true' && needs.parse-blog.outputs.targets != '[]' | ||
| runs-on: ubuntu-latest # Provisions an independent parallel system container node | ||
| strategy: | ||
| fail-fast: false # Prevent crash dependencies: keeps other unit operations processing if one page token fails | ||
| matrix: | ||
| # Converts the Python string output back into a live GitHub Actions looping engine array schema | ||
| target: ${{ fromJson(needs.parse-blog.outputs.targets) }} | ||
| steps: | ||
| - name: Set Facebook Credentials Dynamically # Step 1: Conditional evaluation mapping loop tokens to secure Vault entries | ||
| id: set_tokens # ID marker allowing the cURL step to fetch the selected dynamic parameters | ||
| run: | | ||
| TARGET="${{ matrix.target }}" # Extract current matrix iteration string item token value | ||
| if [ "$TARGET" = "TROOP_331" ]; then | ||
| echo "page_id=${{ secrets.FB_PAGE_ID_TROOP_331 }}" >> $GITHUB_OUTPUT # Maps the unique identification numbers | ||
| echo "access_token=${{ secrets.FB_ACCESS_TOKEN_TROOP_331 }}" >> $GITHUB_OUTPUT # Maps the specific system token | ||
| elif [ "$TARGET" = "PACK_303" ]; then | ||
| echo "page_id=${{ secrets.FB_PAGE_ID_PACK_303 }}" >> $GITHUB_OUTPUT | ||
| echo "access_token=${{ secrets.FB_ACCESS_TOKEN_PACK_303 }}" >> $GITHUB_OUTPUT | ||
| elif [ "$TARGET" = "CREW_303" ]; then | ||
| echo "page_id=${{ secrets.FB_PAGE_ID_CREW_303 }}" >> $GITHUB_OUTPUT | ||
| echo "access_token=${{ secrets.FB_ACCESS_TOKEN_CREW_303 }}" >> $GITHUB_OUTPUT | ||
| elif [ "$TARGET" = "TROOP_303" ]; then | ||
| echo "page_id=${{ secrets.FB_PAGE_ID_TROOP_303 }}" >> $GITHUB_OUTPUT | ||
| echo "access_token=${{ secrets.FB_ACCESS_TOKEN_TROOP_303 }}" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "page_id=${{ secrets.FB_PAGE_ID_DEFAULT }}" >> $GITHUB_OUTPUT # Fallback mappings if special exceptions occur | ||
| echo "access_token=${{ secrets.FB_ACCESS_TOKEN_DEFAULT }}" >> $GITHUB_OUTPUT | ||
| fi | ||
|
|
||
| - name: Send Link to Selected Facebook Page # Step 2: Dispatches feed payload securely onto the Facebook Graph API endpoints | ||
| run: | | ||
| # Formulates absolute permalink layout references ensuring the /blog directory prefix matches your structure | ||
| BLOG_URL="https://brownsburgscouts.org{{ needs.parse-blog.outputs.slug }}" | ||
|
|
||
| # Sets up text message string template payloads using emoticons and line skip variables | ||
| MESSAGE="✍️ New Blog Post: ${{ needs.parse-blog.outputs.title }} | ||
|
|
||
| Read the full article here: $BLOG_URL" | ||
|
|
||
| # Fires cURL POST web request arguments hitting Meta Graph API servers, routing items onto dynamic destinations | ||
| curl -X POST "https://facebook.com{{ steps.set_tokens.outputs.page_id }}/feed" \ | ||
| -d "message=$MESSAGE" \ | ||
| -d "link=$BLOG_URL" \ | ||
| -d "access_token=${{ secrets.FB_ACCESS_TOKEN_DEFAULT }}" # Authorizes operations utilizing isolated access tokens |
Comment on lines
+19
to
+84
| if: contains(github.event.issue.labels.*.name, 'blog') | ||
| runs-on: ubuntu-latest # Provisions a fresh virtual machine host environment | ||
| steps: | ||
| - name: Checkout Code # Step 1: Pulls down your repository code onto the runner | ||
| uses: actions/checkout@v6 # Uses the standard repository checkout action | ||
|
|
||
| - name: Generate GitHub App Token # Step 2: Obtains elevated permissions for automation steps | ||
| id: app-token # ID designation used to query output data in later fields | ||
| uses: actions/create-github-app-token@v3 # Uses GitHub App credentials to bypass workflow limits | ||
| with: | ||
| client-id: ${{ secrets.APP_ID }} # References system secret credentials | ||
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | ||
|
|
||
| - name: Parse Issue Form # Step 3: Extracts individual form values from the issue | ||
| id: parse # ID notation used to fetch variables like jsonString | ||
| uses: stefanbuck/github-issue-parser@v3 | ||
| with: | ||
| template-path: .github/ISSUE_TEMPLATE/01-new-blog-post.yml # Path targeting the source form template | ||
|
|
||
| - name: Set up Python # Step 4: Installs runtime environment for processing scripts | ||
| uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: "3.x" # Always provisions the latest minor build variation of Python 3 | ||
|
|
||
| - name: Install Dependencies # Step 5: Installs mandatory libraries for image/text handling | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install Pillow python-dateutil | ||
|
|
||
| - name: Process Issue Data and Assets (Python Optimizer) # Step 6: Executes data conversion script | ||
| id: process | ||
| env: | ||
| ISSUE_JSON: ${{ steps.parse.outputs.jsonString }} # Maps extracted form fields to environment variables | ||
| run: | | ||
| python .github/scripts/generate_post.py # Script execution target file | ||
|
|
||
| - name: Create Pull Request # Step 7: Opens a staging PR with the new post files | ||
| id: cpr # ID mapping used to fetch the PR URL for issue commenting | ||
| uses: peter-evans/create-pull-request@v8 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} # Grants PR permissions via the generated App token | ||
| commit-message: "feat(blog): add new post from issue #${{ github.event.issue.number }}" | ||
| branch: "automation/issue-${{ github.event.issue.number }}-${{ env.BLOG_FILENAME }}" # Isolated branch path | ||
| title: "feat(blog): ${{ github.event.issue.title }}" # Sets the issue title as the PR title | ||
| body: | # Description text posted within the generated PR body description | ||
| This Pull Request was automatically generated from Issue #${{ github.event.issue.number }}. | ||
| Closes #${{ github.event.issue.number }} | ||
| delete-branch: true # Deletes the branch once the PR is merged into main | ||
| labels: | # Automatically appends tracking tags to the PR | ||
| blog | ||
|
|
||
| - name: Comment on Issue with PR Link # Step 8: Provides clear notification link back to the user | ||
| if: steps.cpr.outputs.pull-request-number != '' # Guard clause ensuring the PR was successfully opened | ||
| uses: peter-evans/create-or-update-comment@v5 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| issue-number: ${{ github.event.issue.number }} | ||
| body: | # Formats notice text targeting the native conversation section | ||
| 🎉 Success! A staging branch has been created. | ||
|
|
||
| Your blog post is ready for review here:👉 ${{ steps.cpr.outputs.pull-request-url }} | ||
|
|
||
| # ---------------------------------------------------------------------------- | ||
| # WORKFLOW 2: Generate Document from Issue | ||
| # ---------------------------------------------------------------------------- | ||
| create-doc: |
Comment on lines
+86
to
+151
| if: contains(github.event.issue.labels.*.name, 'docs') | ||
| runs-on: ubuntu-latest # Provisions a fresh virtual machine host environment | ||
| steps: | ||
| - name: Checkout Code # Step 1: Pulls down your repository code onto the runner | ||
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Generate GitHub App Token # Step 2: Obtains elevated permissions for automation steps | ||
| id: app-token | ||
| uses: actions/create-github-app-token@v3 | ||
| with: | ||
| client-id: ${{ secrets.APP_ID }} | ||
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | ||
|
|
||
| - name: Parse Issue Form # Step 3: Extracts individual form values from the issue | ||
| id: parse | ||
| uses: stefanbuck/github-issue-parser@v3 | ||
| with: | ||
| template-path: ./github/ISSUE_TEMPLATE/02-new-document.yml # Targeting the document form template | ||
|
|
||
| - name: Set up Python # Step 4: Installs runtime environment for processing scripts | ||
| uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: "3.x" | ||
|
|
||
| - name: Install Dependencies # Step 5: Installs mandatory libraries for file handling | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install Pillow | ||
|
|
||
| - name: Process Issue Data and Assets (Python Optimizer) # Step 6: Executes file conversion script | ||
| id: process | ||
| env: | ||
| ISSUE_JSON: ${{ steps.parse.outputs.jsonString }} # Maps extracted form fields to environment variables | ||
| run: | | ||
| python .github/scripts/generate_docs.py | ||
|
|
||
| - name: Create Pull Request # Step 7: Opens a staging PR with the new documentation files | ||
| id: cpr | ||
| uses: peter-evans/create-pull-request@v8 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| commit-message: "feat(docs): add new doc from issue #${{ github.event.issue.number }}" | ||
| branch: "automation/issue-${{ github.event.issue.number }}-${{ env.DOC_FILENAME }}" | ||
| title: "feat(docs): ${{ github.event.issue.title }}" | ||
| body: | | ||
| This Pull Request was automatically generated from Issue #${{ github.event.issue.number }}. | ||
| Closes #${{ github.event.issue.number }} | ||
| delete-branch: true | ||
| labels: | | ||
| docs | ||
|
|
||
| - name: Comment on Issue with PR Link # Step 8: Provides clear notification link back to the user | ||
| if: steps.cpr.outputs.pull-request-number != '' | ||
| uses: peter-evans/create-or-update-comment@v5 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| issue-number: ${{ github.event.issue.number }} | ||
| body: | | ||
| 🎉 Success! A staging branch has been created. | ||
|
|
||
| Your document is ready for review here:👉 ${{ steps.cpr.outputs.pull-request-url }} | ||
|
|
||
| # ---------------------------------------------------------------------------- | ||
| # WORKFLOW 3: Process New Author Request | ||
| # ---------------------------------------------------------------------------- | ||
| add-author-and-pr: |
Comment on lines
+153
to
+244
| if: contains(github.event.issue.labels.*.name, 'new-author') | ||
| runs-on: ubuntu-latest # Provisions a fresh virtual machine host environment | ||
| steps: | ||
| - name: Checkout Repo # Step 1: Pulls down your repository code onto the runner | ||
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Generate Token # Step 2: Obtains elevated permissions for automation steps | ||
| id: app-token | ||
| uses: actions/create-github-app-token@v3 | ||
| with: | ||
| client-id: ${{ secrets.APP_ID }} | ||
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | ||
|
|
||
| - name: Parse Issue Form # Step 3: Extracts profile fields from onboarding issue | ||
| id: parse | ||
| uses: stefanbuck/github-issue-parser@v3 | ||
| with: | ||
| template-path: .github/ISSUE_TEMPLATE/03-new-blog-author.yml # Targeting onboarding template | ||
|
|
||
| - name: Set up Python # Step 4: Installs runtime environment for onboarding script | ||
| uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: "3.x" | ||
|
|
||
| - name: Install Dependencies # Step 5: Installs text/image optimization libraries | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install Pillow PyYAML python-dateutil | ||
|
|
||
| - name: Process Author and Update Files # Step 6: Appends dataset and edits dropdown lists | ||
| id: process_files | ||
| env: | ||
| ISSUE_DATA: ${{ steps.parse.outputs.jsonString }} # Context identifier variable name target | ||
| run: | | ||
| python .github/scripts/add_author.py | ||
|
|
||
| - name: Create Pull Request # Step 7: Opens a staging PR updating configuration maps | ||
| if: success() # Condition check: ensures data steps finished with exit code 0 | ||
| uses: peter-evans/create-pull-request@v8 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| commit-message: "feat: add new blog author via Issue #${{ github.event.issue.number }}" | ||
| title: "feat: add new author from Issue #${{ github.event.issue.number }}" | ||
| body: | | ||
| This PR automatically handles three tasks: | ||
| 1. Downloads, optimizes, and names the avatar image matching the unique author slug. | ||
| 2. Adds the new author data mapping properties into blog/authors.yml. | ||
| 3. Re-generates and sorts the author dropdown options inside the issue templates. | ||
| Closes #${{ github.event.issue.number }}. | ||
| CODEOWNERS have been automatically assigned to review. | ||
| branch: "automation/issue-${{ github.event.issue.number }}" | ||
| delete-branch: true | ||
| labels: | | ||
| new-author | ||
|
|
||
| - name: Comment on Success # Step 8: Alerts author that review process has begun | ||
| if: success() # Condition check: executes only if PR creation succeeds | ||
| uses: peter-evans/create-or-update-comment@v5 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| issue-number: ${{ github.event.issue.number }} | ||
| body: | | ||
| Hi there! An automated Pull Request has been generated to add you to | ||
| the blog authors file and update our submission dropdown tools. The | ||
| project's CODEOWNERS have been notified to review and merge the | ||
| changes. Once merged, your author profile and dropdown options will | ||
| be active! | ||
|
|
||
| - name: Handle Duplicate / Failure # Step 9: Rejects entry and handles automated clean close | ||
| if: failure() # Condition check: executes if scripts fail (e.g., duplicate errors) | ||
| uses: actions/github-script@v9 # Leverages native JavaScript inline engine execution tools | ||
| with: | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} # Standard repo token is fine for closure actions | ||
| script: | # Runs inline Javascript logic against Github API layers | ||
| const issueNumber = context.issue.number; | ||
| // Post notification error explanation comment to issue chat log | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issueNumber, | ||
| body: "🛑 Registration Error: It looks like an author profile with | ||
| this name already exists in blog/authors.yml. Duplicate entries are | ||
| not allowed. This issue will now be closed automatically." | ||
| }); | ||
| // Immediately close out the issue and label it as uncompleted | ||
| await github.rest.issues.update({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issueNumber, | ||
| state: 'closed', | ||
| state_reason: 'not_planned' | ||
| }); No newline at end of file |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.